package com.kaibes.web.api;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.ValueConstants;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import com.kaibes.core.base.annotation.Description;
import com.kaibes.core.base.core.FieldData;
import com.kaibes.core.base.util.ClassUtils;
import com.kaibes.core.base.util.StringUtils;

@Service
public class ApiLinkService implements InitializingBean {

    @Autowired
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    @Autowired(required = false)
    private List<ApiLinkData> temps;
    @Autowired(required = false)
    private List<ApiLinkListener> apiLinkListeners;

    private List<ApiLinkData> apiLinkDatas = new ArrayList<>();
    private Map<String, String> handlerPattern = new HashMap<>();
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();
    
    private Map<String, Object> classStructure(String name) {
        Map<String, Object> model = new HashMap<>();
        try {
            String type = null;
            if (name.contains("<")) {
                int i = name.indexOf("<");
                type = name.substring(i+1, name.length()-1);
                name = name.substring(0, i);
            }
            Class<?> clazz = Class.forName(name);
            if (type != null) {
                if (List.class.isAssignableFrom(clazz)) {
                    model.put("clazzRemark", "数组，"+ClassUtils.getDescription(type));
                } else {
                    model.put("clazzRemark", "未知泛型");
                }
                model.put("fieldDataList", ClassUtils.getListFieldData(Class.forName(type)));
            } else {
                if (clazz == String.class) {
                    model.put("clazzRemark", "字符串结果");
                } else {
                    model.put("clazzRemark", ClassUtils.getDescription(name));
                }
                model.put("fieldDataList", ClassUtils.getListFieldData(clazz));
            }
            if (clazz.isEnum()) {
                model.put("typeDesc", "enum");
            } else {
                model.put("typeDesc", "class");
            }
        } catch (ClassNotFoundException e) {
        }
        return model;
    }

    public List<ApiLinkData> listApi() {
        return listApi(null);
    }

    public List<ApiLinkData> listApi(Boolean isValidated) {
        List<ApiLinkData> dataList = new ArrayList<>();
        for (ApiLinkData apiLinkData : apiLinkDatas) {
        	if (apiLinkData.getType() == ApiLinkType.API) {
        		if (isValidated == null) {
                    dataList.add(apiLinkData);
                } else if (apiLinkData.isValidated() == isValidated) {
                    dataList.add(apiLinkData);
                }
        	}
        }
        return dataList;
    }

    public List<ApiLinkData> listLink() {
        return listLink(null);
    }

    public List<ApiLinkData> listLink(Boolean isValidated) {
        List<ApiLinkData> dataList = new ArrayList<>();
        for (ApiLinkData apiLinkData : apiLinkDatas) {
        	if (apiLinkData.getType() == ApiLinkType.LINK) {
                if (isValidated == null) {
                    dataList.add(apiLinkData);
                } else if (apiLinkData.isValidated() == isValidated) {
                    dataList.add(apiLinkData);
                }
            }
        }
        return dataList;
    }

//    public List<ApiLinkData> listApiByApiLinkLevel(Boolean isValidated) {
//        List<ApiLinkData> dataList = new ArrayList<>();
//        for (ApiLinkData apiLinkData : apiLinkDatas) {
//            if (apiLinkData.getResponseType() != null && apiLinkData.isValidated() == isValidated) {
//                dataList.add(apiLinkData);
//            }
//        }
//        return dataList;
//    }

    public List<ApiLinkData> list(Boolean isValidated) {
        List<ApiLinkData> dataList = new ArrayList<>();
        for (ApiLinkData apiLinkData : apiLinkDatas) {
            if (apiLinkData.isValidated() == isValidated) {
                dataList.add(apiLinkData);
            }
        }
        return dataList;
    }

    public List<ApiLinkData> getApiLinkDatas() {
        return apiLinkDatas;
    }

    public void addApiLinkData(ApiLinkData api) {
        apiLinkDatas.add(api);
    }

    public String getPattern(String method) {
        return handlerPattern.get(method);
    }

    public Optional<ApiLinkData> getByUriAndMethod(String uri, String method) {
        return apiLinkDatas.stream().filter(data -> {
            if (Objects.equals(data.getMethod(), method)) {
                for (String pattern : data.getPatternList()) {
                    if (antPathMatcher.match(pattern, uri)) {
                        return true;
                    }
                }
            }
            return false;
        }).findFirst();
    }

    public Optional<ApiLinkData> getByName(String name) {
        return apiLinkDatas.stream().filter(data -> Objects.equals(data.getName(), name)).findFirst();
    }

	@SuppressWarnings("unchecked")
	@Override
	public void afterPropertiesSet() throws Exception {
		if (temps != null) {
            apiLinkDatas.addAll(temps);
        }

        Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> map : handlerMethods.entrySet()) {
            HandlerMethod handlerMethod = map.getValue();

            Class<?> controllerClass = handlerMethod.getBeanType();
            if (controllerClass.getName().startsWith("org.springframework")) {
                continue;
            }

            Method method = handlerMethod.getMethod();
            if (!method.isAnnotationPresent(ApiLink.class)) {
                continue;
            }

            ApiLink apiLinkI = method.getDeclaredAnnotation(ApiLink.class);

            ApiLinkData apiLink = new ApiLinkData();
            apiLink.setValidated(apiLinkI.validated());
            apiLink.setRequestDesc(new ApiLinkTypeDesc());
            apiLink.setResponseDesc(new ApiLinkTypeDesc());
            
            apiLink.getResponseDesc().setRemark(apiLinkI.value());

            RequestMappingInfo info = map.getKey();
            RequestMethodsRequestCondition methodsCondition = info.getMethodsCondition();
            HeadersRequestCondition headerCondition = info.getHeadersCondition();

            String httpMethod = methodsCondition.toString();
            if (httpMethod != null && httpMethod.startsWith("[") && httpMethod.endsWith("]")) {
                httpMethod = httpMethod.substring(1, httpMethod.length() - 1);
            }
            apiLink.setMethod(httpMethod);

            String headers = headerCondition.toString();
            if (headers != null && headers.startsWith("[") && headers.endsWith("]")) {
                headers = headers.substring(1, headers.length() - 1);
            }
            apiLink.setHeaders(headers);

            PatternsRequestCondition p = info.getPatternsCondition();

            Type argClass = null;
            Parameter[] parameters = method.getParameters();
            List<ParamValue> pathValues = new ArrayList<>();
            List<ParamValue> paramValues = new ArrayList<>();
            for (Parameter parameter : parameters) {
                if (parameter.isAnnotationPresent(RequestBody.class)) {
                    argClass = parameter.getParameterizedType();
                    String type = argClass.getTypeName();
                    apiLink.getRequestDesc().setType(type);
                    if (parameter.isAnnotationPresent(Description.class)) {
                        String remark = parameter.getDeclaredAnnotation(Description.class).value();
                        apiLink.getRequestDesc().setRemark(remark);
                    } else if (StringUtils.isNotBlank(type)) {
                        String remark = ClassUtils.getDescription(type);
                        apiLink.getRequestDesc().setRemark(remark);
                    }
                } else if (parameter.isAnnotationPresent(PathVariable.class)) {
                    ParamValue pathValue = new ParamValue();
                    PathVariable pathVariable = parameter.getDeclaredAnnotation(PathVariable.class);
                    if (pathVariable.name().isEmpty()) {
                        pathValue.setName(parameter.getName());
                    } else {
                        pathValue.setName(pathVariable.name());
                    }
                    if (parameter.isAnnotationPresent(Description.class)) {
                        pathValue.setValue(parameter.getDeclaredAnnotation(Description.class).value());
                    } else {
                        pathValue.setValue("");
                    }
                    pathValue.setRequired(pathVariable.required());
                    pathValues.add(pathValue);
                } else if (parameter.isAnnotationPresent(RequestParam.class)) {
                    ParamValue paramValue = new ParamValue();
                    RequestParam requestParam = parameter.getDeclaredAnnotation(RequestParam.class);
                    if (requestParam.name().isEmpty()) {
                        paramValue.setName(parameter.getName());
                    } else {
                        paramValue.setName(requestParam.name());
                    }
                    if (parameter.isAnnotationPresent(Description.class)) {
                        paramValue.setValue(parameter.getDeclaredAnnotation(Description.class).value());
                    } else {
                        paramValue.setValue("");
                    }
                    if (!Objects.equals(ValueConstants.DEFAULT_NONE, requestParam.defaultValue())) {
                        paramValue.setRequired(false);
                    } else {
                        paramValue.setRequired(requestParam.required());
                    }
                    paramValues.add(paramValue);
                }
            }
            apiLink.setPathValues(pathValues);
            apiLink.setParamValues(paramValues);

            if (controllerClass.isAnnotationPresent(RestController.class)
                    || controllerClass.isAnnotationPresent(ResponseBody.class)) {
            	apiLink.setType(ApiLinkType.API);
                if (method.getReturnType() != ResponseEntity.class) {
                    apiLink.getResponseDesc().setType(method.getReturnType().getName());
                } else {
                    Type superClass = method.getGenericReturnType();
                    ParameterizedType ptype = (ParameterizedType) superClass;
                    String name = ptype.getActualTypeArguments()[0].getTypeName();
                    if (StringUtils.equals(name, "java.lang.Object", "?")) {
                        apiLink.getResponseDesc().setType("");
                    } else {
                        apiLink.getResponseDesc().setType(name);
                    }
                }
            } else {
            	apiLink.setType(ApiLinkType.LINK);
            }
            apiLink.setPatternList(new ArrayList<>(p.getPatterns()));
            apiLink.setName(apiLink.getPatternList().toString() + "_" + apiLink.getMethod());
            
            String type = apiLink.getRequestDesc().getType();
            if (StringUtils.isNotBlank(type)) {
                Map<String, Object> requestMap = classStructure(type);
                apiLink.getRequestDesc().setFieldList((List<FieldData>)requestMap.get("fieldDataList"));
                apiLink.getRequestDesc().setTypeDesc((String)requestMap.get("typeDesc"));
                apiLink.getRequestDesc().setTypeRemark((String)requestMap.get("clazzRemark"));
            }
            
            type = apiLink.getResponseDesc().getType();
            if (StringUtils.isNotBlank(type) && !"void".equals(type)) {
                Map<String, Object> responseMap = classStructure(type);
                apiLink.getResponseDesc().setFieldList((List<FieldData>)responseMap.get("fieldDataList"));
                apiLink.getResponseDesc().setTypeDesc((String)responseMap.get("typeDesc"));
                apiLink.getResponseDesc().setTypeRemark((String)responseMap.get("clazzRemark"));
            }
            
            apiLinkDatas.add(apiLink);
        }

        if (apiLinkListeners != null) {
            apiLinkListeners.forEach(listener -> {
                listener.afterInit(apiLinkDatas);
            });
        }
	}

	public void setValidatedByName(boolean validated, String...names) {
		for (String name : names) {
			getByName(name).ifPresent(obj->obj.setValidated(validated));
		}
	}
}
